package com.onLineCar.dispatch.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.onLineCar.common.core.domain.Result;
import com.onLineCar.dispatch.entity.DispatchVehicle;
import com.onLineCar.dispatch.entity.dto.DispatchVehicleDTO;
import com.onLineCar.dispatch.entity.orderEnum.OrderStatusEnum;
import com.onLineCar.dispatch.entity.redis.RedisGeoCommand;
import com.onLineCar.dispatch.entity.request.DispatchVehicleRequest;
import com.onLineCar.dispatch.entity.utils.PointConvertStringUtil;
import com.onLineCar.dispatch.entity.volidEnum.ValidateEnum;
import com.onLineCar.dispatch.exception.ServiceException;
import com.onLineCar.dispatch.job.dto.DispatchVehicleJobDTO;
import com.onLineCar.dispatch.mapper.DispatchVehicleMapper;
import com.onLineCar.dispatch.mq.QueueConfig;
import com.onLineCar.dispatch.rpc.MapRPC;
import com.onLineCar.dispatch.rpc.dto.ApiVehicleInfoDTO;
import com.onLineCar.dispatch.rpc.dto.TblOrder;
import com.onLineCar.dispatch.rpc.impl.RpcServiceImpl;
import com.onLineCar.dispatch.service.DispatchVehicleService;
import com.onLineCar.dispatch.service.HttpService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import static com.onLineCar.dispatch.entity.constant.Constant.*;
import static com.onLineCar.dispatch.exception.ServiceCodeEnum.MAP_SERVICE_NOT_SUPPORT;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhucaiyi
 * @since 2023-12-20
 */
@Service
@Slf4j
@SuppressWarnings("all")
public class DispatchVehicleServiceImpl extends ServiceImpl<DispatchVehicleMapper, DispatchVehicle> implements DispatchVehicleService {

    @Resource
    private HttpService httpService;
    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private RpcServiceImpl rpcService;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private MapRPC mapRPC;

    /**
     * io密集型
     */
    private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            32, // 核心线程数
            50, // 最大线程数
            60, // 线程池维护线程所允许的空闲时间
            TimeUnit.SECONDS, // 时间单位
            new LinkedBlockingQueue<>(),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result dispatchVehicleSendOrder(DispatchVehicleRequest dispatchVehicleRequest) {
        // 1 前置校验
        Long userId = dispatchVehicleRequest.getUserDTO().getUserId();
        Long orderNum = dispatchVehicleRequest.getOrderDTO().getOrderNum();
        Point startPoint = dispatchVehicleRequest.getUserDTO().getStartPoint();
        String finalAddress = getAddress(startPoint, userId.toString(), orderNum.toString());
        Integer districtCode = getDistrictCode(startPoint, userId.toString(), orderNum.toString());
        ConcurrentHashMap<Integer, Boolean> concurrentHashMap = new ConcurrentHashMap<>();

        CompletableFuture.allOf(
                // 1.2 当前 城市是否有可服务的司机 调用车辆调度
                CompletableFuture.runAsync(() -> {
                    concurrentHashMap.put(ValidateEnum.HAVE_DRIVER.getValidateCode(), rpcService.driverService(finalAddress));
                }, executor),
                // 1.3 该城市是否有对应的计费规则 调用计费服务
                CompletableFuture.runAsync(() -> {
                    concurrentHashMap.put(ValidateEnum.HAVE_BILLING_RULE.getValidateCode(), rpcService.havaBillingRule(finalAddress));
                }, executor),
                // 1.4 判断此用户是否是黑名单用户 调用用户服务
                CompletableFuture.runAsync(() -> {
                    concurrentHashMap.put(ValidateEnum.IS_BLOCK_USER.getValidateCode(), rpcService.isBlockUser(dispatchVehicleRequest.getUserDTO().getUserId()));
                }, executor),
                // 1.5 判断此用户的账户是否有之前的订单还未支付完成 调用订单服务
                CompletableFuture.runAsync(() -> {
                    concurrentHashMap.put(ValidateEnum.HAVE_OTHER_ORDER.getValidateCode(), rpcService.queryOrderByUserId(dispatchVehicleRequest.getUserDTO().getUserId()));
                }, executor));

        Iterator<Map.Entry<Integer, Boolean>> iterator;
        for (iterator = concurrentHashMap.entrySet().iterator(); iterator.hasNext(); ) {
            Map.Entry<Integer, Boolean> entry = iterator.next();
            Boolean value = entry.getValue();
            if (!value) {
                String validateDesc = ValidateEnum.getValidateDescByCode(entry.getKey());
                log.error("提示信息:{} userId:{}, orderNum:{}", validateDesc, dispatchVehicleRequest.getUserDTO().getUserId().toString()
                        , dispatchVehicleRequest.getOrderDTO().getOrderNum());
                return Result.success(null, ValidateEnum.getValidateDescByCode(entry.getKey()));
            }
        }
        // todo 判断用车时间是否大于30分钟
        // 派单逻辑 :
        //a、首先筛选2公里内是否有同级别、同城市的司机
        Integer kilometers = 2;
        List<RedisGeoCommand> redisGeoCommands = getRedisGeoCommands(userId.toString(), orderNum.toString(), startPoint, kilometers, districtCode, NumberUtils.INTEGER_ZERO);
        if (CollectionUtil.isEmpty(redisGeoCommands)) {
            log.info("周边无司机可派，将此数据持久化");
            saveDipatchVehicle(dispatchVehicleRequest);
            return Result.success(null, "目前附近没有可派车辆，正从远处调度，请稍等......");
        }
        List<Integer> vehicleIds = redisGeoCommands.stream().map(r -> {
            return Integer.parseInt(r.getGeoLocation().getName().split(SPLIT_STR)[3]);
        }).toList();
        HashMap<String, String> driverAndVehicleIdMapper = new HashMap<>();
        redisGeoCommands.stream().forEach(f -> {
            String[] geoRedisKeys = f.getGeoLocation().getName().split(SPLIT_STR);
            String driverId = geoRedisKeys[1];
            String vehiceId = geoRedisKeys[3];
            driverAndVehicleIdMapper.put(vehiceId, driverId);
        });
        // 调用订单模块获取车辆级别
        TblOrder tblOrder = rpcService.queryOrderByVehicleIds(orderNum.toString());
        if (ObjectUtils.isEmpty(tblOrder)) {
            log.error("订单不存在 orderNum：{}", orderNum.toString());
            throw new ServiceException("订单不存在");
        }
        List<String> driverIds = Lists.newArrayList();
        // 调用车辆管理获取车辆级别
        List<ApiVehicleInfoDTO> vehicleInfoDTOList = rpcService.queryVehicleInfoByVehicleIds(vehicleIds);
        vehicleInfoDTOList.forEach(f -> {
            if (f.getVehicleTypeId().toString().equals(tblOrder.getVehicleLevel().toString())) {
                driverIds.add(driverAndVehicleIdMapper.get(f.getCarId().toString()));
            }
        });
        if (CollectionUtil.isEmpty(driverIds)) {
            log.info("周边司机车辆级别不匹配，将此数据持久化");
            saveDipatchVehicle(dispatchVehicleRequest);
            return Result.success(null, "目前附近没有匹配车辆等级，正从远处调度，请稍等......");
        }
        DispatchVehicleDTO dispatchVehicleDTO = new DispatchVehicleDTO().setOrderDTO(dispatchVehicleRequest.getOrderDTO())
                .setUserDTO(dispatchVehicleRequest.getUserDTO())
                .setDriverId(driverIds);

        // 发送订单给司机
        rabbitTemplate.convertAndSend(QueueConfig.DISPATCH_EXCHANGE_NAME, QueueConfig.DISPATCH_ROUTING_KEY, JSON.toJSONString(dispatchVehicleDTO));
        return Result.success(null, "目前已将订单发送给司机，请等待司机接单");
        //todo c、用车时间前(开始立即用车派单逻辑所设置的时间 )的时间和用车时间1.5小时内无待服务订单(配置项)
        //todo d、车辆电量剩余里程-订单预估里程大于等于20公里(可配置，非配置项)
    }

    private Integer getDistrictCode(Point startPoint, String userId, String orderNum) {
        String address = getAddress(startPoint, userId, orderNum);
        String finalAddress = address;
        CompletableFuture<Integer> supportCityFuture = CompletableFuture.supplyAsync(() -> {
            Integer supportCity = rpcService.querySupportCity(finalAddress);
            return supportCity;
        }, executor);
        Integer districtCode = null;
        try {
            districtCode = supportCityFuture.get();
        } catch (Exception e) {
            log.error("目前地区不支持打车 userId:{}, orderNum:{}", userId
                    , orderNum);
            throw new ServiceException(MAP_SERVICE_NOT_SUPPORT);
        }
        return districtCode;
    }

    private String getAddress(Point startPoint, String userId, String orderNum) {
        CompletableFuture<String> addresscompletableFuture = CompletableFuture.supplyAsync(() -> {
            StringBuilder pointStrBuilder = new StringBuilder();
            pointStrBuilder.append(startPoint.getX())
                    .append(APPEND_STR)
                    .append(startPoint.getY());
            return rpcService.getAddressByLocation(pointStrBuilder);
        }, executor);

        String address = null;
        try {
            address = addresscompletableFuture.get();
        } catch (Exception e) {
            log.error("调用地图服务异常 userId:{}, orderNum:{}", userId
                    , orderNum);
            throw new RuntimeException(e);
        }
        return address;
    }

    /**
     * @param string
     * @param s
     * @param startPoint
     * @param kilometers
     * @param districtCode
     * @param flag         0:为尝试派单 1：后台持续派单
     * @return
     */
    private List<RedisGeoCommand> getRedisGeoCommands(String userId, String orderNum, Point startPoint, Integer kilometers, Integer districtCode, Integer flag) {
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = retrieveAttachmentsDriver(startPoint, kilometers, districtCode);
        // 若无则筛选4公里内是否有同级别同城市的司机
        if (CollectionUtil.isEmpty(results.getContent())) {
            log.warn("两公里无司机，扩大范围为四公里 userId:{}, orderNum:{}, NowTime:{}", userId, orderNum, DateTime.now());
            kilometers = kilometers << 1;
            results = retrieveAttachmentsDriver(startPoint, kilometers, districtCode);
        }
        // 若无则筛选8公里内是否有同级别、同城市的司机b、司机状态为听单中、听顺风单
        if (CollectionUtil.isEmpty(results.getContent())) {
            log.warn("四公里无司机，扩大范围为八公里 userId:{}, orderNum:{}, NowTime:{}", userId, orderNum, DateTime.now());
            kilometers = kilometers << 1;
            results = retrieveAttachmentsDriver(startPoint, kilometers, districtCode);
        }
        // 若无则筛选16公里内是否有同级别、同城市的司机b、司机状态为听单中、听顺风单
        if (CollectionUtil.isEmpty(results.getContent()) && flag.equals(NumberUtils.INTEGER_ONE)) {
            log.warn("八公里无司机，扩大范围为十六公里 userId:{}, orderNum:{}, NowTime:{}", userId, orderNum, DateTime.now());
            kilometers = kilometers << 1;
            results = retrieveAttachmentsDriver(startPoint, kilometers, districtCode);
        }
        List<RedisGeoCommand> redisGeoCommands = RedisGeoCommand.convertRedisGeoCommand(results);
        return redisGeoCommands;
    }

    @Override
    public void dispatchVehicleScheduled() {
        LambdaQueryWrapper<DispatchVehicle> dispatchVehicleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        dispatchVehicleLambdaQueryWrapper.eq(DispatchVehicle::getStatus, OrderStatusEnum.ORDER_STATUS_WAIT.getOrderCode());
        List<DispatchVehicle> dispatchVehicleList = this.list(dispatchVehicleLambdaQueryWrapper);
        Integer kilometers = 2;
        if (CollectionUtil.isNotEmpty(dispatchVehicleList)) {
            dispatchVehicleList.forEach(f -> {
                Point stringToPoint = PointConvertStringUtil.convertStringToPoint(f.getStartPoint());
                Long userId = f.getUserId();
                Long orderNum = f.getOrderNum();
                List<RedisGeoCommand> redisGeoCommands = getRedisGeoCommands(userId.toString(), orderNum.toString(), stringToPoint, kilometers, getDistrictCode(stringToPoint, f.getUserId().toString(), f.getOrderNum().toString()), NumberUtils.INTEGER_ZERO);
                if (CollectionUtil.isNotEmpty(redisGeoCommands)) {
                    TblOrder tblOrder = rpcService.queryOrderByVehicleIds(orderNum.toString());
                    if (ObjectUtils.isEmpty(tblOrder)) {
                        log.error("订单不存在 orderNum：{}", orderNum.toString());
                    }
                    HashMap<String, String> driverAndVehicleIdMapper = new HashMap<>();
                    redisGeoCommands.stream().forEach(redisGeoCommand -> {
                        String[] geoRedisKeys = redisGeoCommand.getGeoLocation().getName().split(SPLIT_STR);
                        String driverId = geoRedisKeys[1];
                        String vehiceId = geoRedisKeys[3];
                        driverAndVehicleIdMapper.put(vehiceId, driverId);
                    });
                    List<Integer> vehicleIds = redisGeoCommands.stream().map(r -> {
                        return Integer.parseInt(r.getGeoLocation().getName().split(SPLIT_STR)[3]);
                    }).toList();
                    // 调用车辆管理获取车辆级别
                    List<String> driverIds = Lists.newArrayList();
                    List<ApiVehicleInfoDTO> vehicleInfoDTOList = rpcService.queryVehicleInfoByVehicleIds(vehicleIds);
                    vehicleInfoDTOList.forEach(vehicleId -> {
                        if (vehicleId.getVehicleTypeId().toString().equals(tblOrder.getVehicleLevel().toString())) {
                            driverIds.add(driverAndVehicleIdMapper.get(vehicleId.getCarId().toString()));
                        }
                    });
                    if (CollectionUtil.isNotEmpty(driverIds)) {
                        DispatchVehicleJobDTO dispatchVehicleJobDTO = new DispatchVehicleJobDTO()
                                .setDriverList(driverIds)
                                .setUserId(userId.toString())
                                .setOrderNum(orderNum.toString());
                        // 发送订单给司机
                        log.warn("已将订单发送给司机 userId:{}, orderNum:{}, NowTime:{}", userId, orderNum, DateTime.now());
                        rabbitTemplate.convertAndSend(QueueConfig.DISPATCH_EXCHANGE_NAME, QueueConfig.DISPATCH_ROUTING_KEY, JSON.toJSONString(dispatchVehicleJobDTO));
                        this.updateById(f.setStatus(OrderStatusEnum.ORDER_STATUS_SEND.getOrderCode()));
                    }
                } else {
                    log.info("目前附近没有匹配车辆等级，正从远处调度，请稍等......");
                }
            });
        }
    }

    @Override
    public Result<String> receiveOrderCallback(HashMap<String, String> receiverOrderMap) {
        return null;
    }

    private void saveDipatchVehicle(DispatchVehicleRequest dispatchVehicleRequest) {
        DispatchVehicle dispatchVehicle = dispatchVehicleRequest.convertDispatchVehicle(dispatchVehicleRequest, OrderStatusEnum.ORDER_STATUS_WAIT.getOrderCode());
        this.save(dispatchVehicle);
    }

    private GeoResults<RedisGeoCommands.GeoLocation<String>> retrieveAttachmentsDriver(Point startPoint, Integer kilometers, Integer districtCode) {
        Circle circle = new Circle(startPoint, new Distance(kilometers, Metrics.KILOMETERS));//Point(经度, 纬度) Distance(距离量, 距离单位)
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(5);
        return redisTemplate.opsForGeo()
                .radius(CITY_STR + districtCode, circle, args);
    }

    public static void main(String[] args) {
        double ceil = Math.ceil(1.5);
        double floor = Math.floor(1.5);
        double abs = Math.abs(1.2);
        int i = 2 << 1;
        int i1 = i << 1;
//        System.out.println(" " + i);
        System.out.println(" " + i1);
        System.out.println("2 left 1位：" + i);
        System.out.println("abs:" + abs);
        System.out.println("ceil:" + ceil);
        System.out.println("floor:" + floor);
        System.out.println("nowTime: " + DateTime.now());
    }
}
